import java.sql.Array;
import java.util.Deque;
import java.util.LinkedList;

public class Sort {
    /**
     * 交换
     * @param array
     * @param i
     * @param j
     */
    private static void swap(int[] array, int i, int j) {
        int tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
    }
    /**
     * 直接插入排序
     * @param array
     */
    public static void insertSort(int[] array) {
        //从下标1开始遍历
        for (int i = 1; i < array.length; i++) {

            //每次把 i 下标位置的值存放到tmp中
            int tmp = array[i];

            //每次tmp都要与i前一个下标的值进行比较
            int j = i - 1;

            //j 的值每次都要从 i 的前一个位置一直往左边比较
            for (; j >= 0; j--) {

                //每次拿到j的值与tmp进行比较，如果大于tmp，则与i的位置进行交换
                if (array[j] > tmp) {
                    array[j+1] = array[j];
                }else {
                    //小于则把tmp的值放回到i的位置
                    array[j+1] = tmp;  //这段代码可以省略
                    break;
                }
            }

            //j<0时，把tmp的值放回到i的值
            array[j+1] = tmp;
        }
    }


    /**
     * 希尔排序
     * @param array
     */
    public static void shellSort(int[] array) {
        //定义gap将数组的长度赋值给这个变量
        int gap = array.length;

        //设置循环, 每次使gap除以2, 等于1时, 说明有序, 跳出循环
        while (gap > 1) {
            gap = gap / 2;

            //调用直接插入排序, 根据步长进行调整
            shell(array,gap);
        }
    }



    private static void shell(int[] array, int gap) {
        //遍历数组, i变量从gap下标位置开始
        for (int i = gap; i < array.length; i++) {
            //每次把 i 下标位置的值存放到tmp中
            int tmp = array[i];

            //j变量从i - gap位置开始
            int j = i - gap;

            //j变量要每次减去gap个位置, j此时的位置要是负数就比较j和tmp的值
            for (; j >= 0; j-=gap) {

                //每次拿到j的值与tmp进行比较，如果大于tmp，则与i的位置进行交换
                if (array[j] > tmp) {
                    array[j+gap] = array[j];
                }else {
                    //小于则跳出循环
                    break;
                }
            }
            //j<0时，把tmp的值放回到i的值
            array[j+gap] = tmp;
        }
    }

    /**
     * 选择排序
     * @param array
     */
    public static void selectSort(int[] array) {
        //数组的第一个元素开始, 依次遍历到数组的最后一个元素, 拿到最小值
        for (int i = 0; i < array.length; i++) {

            //将元素的索引标记为min
            int min = i;

            //每次都从i+1下标开始进行遍历
            for (int j = i+1; j < array.length; j++) {

                //元素大小进行比较
                if (array[min] > array[j]) {
                    //每次将最小值的索引赋值给min
                    min = j;
                }
            }

            //内层循环结束后, 此时min的索引为最小值
            //与i下标元素的值进行交换
            swap(array,i,min);
        }
    }


    /**
     * 堆排序
     * @param array
     */
    public static void heapSort(int[] array) {

        //创建大根堆
        createHeap(array);

        //获取大根堆中最后一个元素
        int end = array.length-1;

        //当大根堆的下标为 0 时, 说明只剩一个元素, 循环结束
        while (end > 0) {

            //交换堆顶元素和最后一个堆元素, 此时最后一个元素为堆中最大值
            swap(array,0,end);

            //交换元素后向下调整, 使其成为大根堆
            siftDown(array,0,end);
    
            //将最大值元素与堆断开连接
            end--;
        }

    }

    //创建大根堆
    private static void createHeap(int[] array) {
        //每次取最后一个 父母节点, 开始向下调整
        for (int parent = (array.length-1-1)/2; parent >= 0; parent--) {
            siftDown(array,parent,array.length);
        }
    }

    //向下调整
    private static void siftDown(int[] array,int parent, int len){
        //左孩子的节点
        int child = 2 * parent + 1;

        //只要 孩子节点 小于数组的长度, 说明 孩子节点 没有越界
        while (child < len) {

            //如果 左孩子节点 小于 右孩子节点,取 右孩子节点 的下标
            if (child+1 < len && array[child] < array[child+1]) {
                child++;
            }

            //最大孩子节点 与 父母节点 大小进行比较, 如果大于 父母节点,则交换
            if (array[child] > array[parent]) {

                //交换 最大孩子节点 与 父母节点 的值
                swap(array,child,parent);

                //把 孩子节点 赋值给 父母节点
                parent = child;

                //左孩子节点
                child = 2 * parent + 1;
            }else { //说明 父母节点 为最大值, 不用交换, 跳出循环
                break;
            }

        }

    }

    /**
     * 冒泡排序
     * @param array
     */
    public static void bubbleSort(int[] array) {
        for (int i = 0; i < array.length-1; i++) {
            boolean flg = false;
            for (int j = 0; j < array.length-1-i; j++) {
                if (array[j] > array[j+1]) {
                    swap(array,j,j+1);
                    flg = true;
                }
            }
            if(!flg) {
                return;
            }

        }
    }


    /**
     * 快速排序 挖坑法
     * @param array
     */
    public static void quickSort(int[] array) {
        quick1(array,0,array.length-1);
    }

    private static void quick1(int[] array, int start, int end) {
        if (start >= end) {
            return;
        }

        //进行三数取中 找到下标
//        int index = threeMid(array,start,end);
//        swap(array,start,index);

        int par = parttion(array,start,end);

        quick1(array,start,par-1);

        quick1(array,par+1,end);

    }

    private static int parttion(int[] array, int left, int right) {
        int tmp = array[left];
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];

            while (left < right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }

//    //三数取中
//    private static int threeMid(int[] array,int left,int right) {
//        int mid = (left+right)/2;
//        if (array[left] < array[right]) {
//            if (array[mid] < array[left]) {
//                return left;
//            } else if (array[mid] > array[right]) {
//                return right;
//            } else {
//                return mid;
//            }
//        } else {
//            //array[left] > array[right]
//            if (array[mid] > array[left]) {
//                return left;
//            } else if (array[mid] < array[right]) {
//                return right;
//            } else {
//                return mid;
//            }
//        }
//    }




    /**
     * 快速排序 Hoare法
     * @param array
     */
    public static void quickSortHoare(int[] array) {
        quick2(array,0,array.length-1);
    }

    private static void quick2(int[] array, int start, int end) {
        if (start >= end) {
            return;
        }

//        //进行三数取中
//        int index = threeMid(array,start,end);
//        swap(array,start,index);

        int par = parttionHoare(array,start,end);

        quick2(array,start,par-1);

        quick2(array,par+1,end);

    }

    private static int parttionHoare(int[] array, int left, int right) {
        //以首元素作为排序条件 将比首元素小的放左边 比首元素大的放右边
        int key = array[left];

        //记录首元素的下标
        int i = left;

        while (left < right) {
            while (left < right && array[right] >= key) {
                right--;
            }

            while (left < right && key >= array[left]) {
                left++;
            }

            swap(array,left,right);
        }

        swap(array,i,left);
        return left;
    }


    /**
     * 快速排序 非递归实现
     * @param array
     */
    public static void quickSortNor(int[] array) {
        int start = 0;
        int end = array.length-1;
        int par = parttion(array,start,end);

        Deque<Integer> stack = new LinkedList<>();
        //左边有2个数对
        if (par > start+1) {
            stack.push(start);
            stack.push(par-1);
        }

        //右边有两个数对
        if (par < end-1) {
            stack.push(par+1);
            stack.push(end);
        }

        while (!stack.isEmpty()) {
            end = stack.pop();
            start = stack.pop();
            par = parttion(array,start,end);

            //左边有2个数对
            if (par > start+1) {
                stack.push(start);
                stack.push(par-1);
            }

            //右边有两个数对
            if (par < end-1) {
                stack.push(par+1);
                stack.push(end);
            }
        }

    }

/*    private static int parttion(int[] array, int left, int right) {
        int tmp = array[left];
        while (left < right) {
            while (left < right && array[right] >= tmp) {
                right--;
            }
            array[left] = array[right];

            while (left < right && array[left] <= tmp) {
                left++;
            }
            array[right] = array[left];
        }
        array[left] = tmp;
        return left;
    }*/

    /**
     * 归并排序
     * @param array
     */
    public static void mergeSort(int[] array) {
        mergeSortChild(array,0,array.length-1);
    }

    private static void mergeSortChild(int[] array, int left, int right) {
        if (left >= right) {
            return;
        }

        int mid = (left+right)/2;

        mergeSortChild(array,left,mid);

        mergeSortChild(array,mid+1,right);

        merge(array,left,mid,right);
    }

    //合并数组
    private static void merge(int[] array, int left, int mid, int right) {
        int[] tmp = new int[right-left+1];
        int k = 0;

        int s1 = left;
        int e1 = mid;
        int s2 = mid+1;
        int e2 = right;

        //判断两个数组中 都有数据
        while (s1 <= e1 && s2 <= e2) {
            if (array[s1] <= array[s2]) {
                tmp[k++] = array[s1++];
            }else {
                tmp[k++] = array[s2++];
            }
        }

        while (s1 <= e1) {
            tmp[k++] = array[s1++];
        }

        while (s2 <= e2) {
            tmp[k++] = array[s2++];
        }

        for (int i = 0; i < tmp.length; i++) {
            array[i+left] = tmp[i];
        }
    }

    /**
     * 归并排序 非递归实现
     * @param array
     */
    public static void mergeSortNor(int[] array) {
        int gap = 1;

        while (gap < array.length) {
            for (int i = 0; i < array.length; i = i + 2*gap) {
                int left = i;
                int mid = left+gap-1;
                if (mid >= array.length) {
                    mid = array.length-1;
                }
                int right = mid+gap;
                if (right >= array.length) {
                    right = array.length-1;
                }

                merge(array,left,mid,right);
            }
            gap *= 2;
        }
    }

}
